6주차 - 시큐리티
개요
이전 것들 중 하고 싶은 거.
로컬 클러스터 고쳤으니까, vpa 인플레이스 교체 하고 싶다.
마침 containered 2로 업글해서 recursivereadmount도 실험 가능
시간 남으면 왜 initconfiguration 파일을 줄 때 문제가 생겼는지 알아봐야 함.
그래야 nftables 실험 가능
인증서 구조
인증서 구조
간단하게 쿠버의 인증서 분석하기
이건 로컬로 하는게 낫다.
우리가 흔히 사용하는 kubectl은 .kube/config
파일의 정보를 통해 이뤄진다.
k config view
간단하게 현재 kubeconfig 파일을 상태를 확인할 수 있는데, 다음의 구조로 된 것을 파악할 수 있다.
cat ~/.kube/config
실제로 해당 파일을 뜯어보면 생략되지 않은 날 것의 데이터를 볼 수 있다.
처음 클러스터를 세팅할 때 나오는 kubeconfig 파일은 유저의 신원을 인증서로 검증한다는 것을 확인할 수 있다.
-data
로 끝나는 필드는 실제 인증서 정보가 base64로 인코딩돼있다.
(이렇게 끝나지 않으면 파일 경로를 명시할 때 쓰이는 필드가 된다.)
클러스터 쪽의 데이터를 디코딩해보면 이렇게 인증서가 들어있는 것이 보인다.
echo "데이터" | base64 -d | openssl x509 -text -noout
간단하게 인증서 내용을 꺼내본다.
공개키 알고리즘으로는 rsa가 사용됐고, 짧은 비트의 rsa는 취약하다고 알려졌기에 보통 2048비트짜리 키를 만든다.
10년 짜리.. 인증서이고 주체는 kubernetes이다.
또한 발행자 역시 kubernetes로, 이 인증서는 root 인증서임을 알 수 있다.
실제로 이걸 이용해 curl 요청을 날리게 되면 신뢰할 수 없는 루트라는 경고가 출력된다.
확장 정보로, 이 주체 키 식별자가 표시된는데, 이건 아래에서 비교해볼 것이다.
같은 방식으로 유저의 인증서도 꺼내본다.
이번에는 kubernetes-admin이란 유저가 cluster-admins란 그룹에 속해있는 상태로, kubernetes에 의해 서명 받은 인증서가 나온다.
이 인증서의 확장 정보에는 이 인증서에 서명한 인증기관 식별자가 담겨 있다.
이 값은 위에서 본 쿠버네티스 루트 인증서의 식별자와 정확히 일치하는 것을 확인할 수 있다.
결과적으로, 최소한 이 config 파일을 가지고 통신할 때 api 서버는 유저의 신원을 이 인증서를 통해 검증한다는 것을 알 수 있다.
다음 주제로 다룰 것이지만, 인증서 방식 말고도 api서버가 들어오는 요청의 신원을 검증하는 방법은 다양한데, 가장 기본적인 방식이 바로 x509인증서이다.
그럼 어떻게 이 인증서가 발급된 걸까?
그 과정은 클러스터의 부트스트랩 과정을 까보면 알 수 있다.
이건 kubeadm의 부트스트랩 과정의 초반 부분이다.
초반에 각종 컴포넌트들이 만들어지기 이전에 인증서들과, config 파일이 만들어지는 것을 확인할 수 있다.
이 과정 상에서 root 인증서가 만들어지고, 이것에 체인이 되는 하위 인증서들이 파생되어 만들어진다.
그 인증서 흔적들은 여기에서 확인할 수 있다.
CSR 리소스 사용하여 특정 신원으로 api 조작하기
x509로 사용자를 쉽게 클러스터에 인증시키는 방법 중 하나는 쿠버네티스에서 제공하는 CSR 리소스를 이용하는 것이다.
방법도 꽤나 간단한데, 자체 키를 만들고 이걸 서명해달라 찡찡대면 된다!
한번 임의의 유저를 가정하고, 그 유저를 관리자 권한을 가지게 해보자.
가장 쉬운 방법은 해당 유저를 system:masters
그룹에 속한다고 표시하는 것이다.
그러나 masters는 rbac관련 제한을 받지 않는, 막강한 권한을 가지게 되는 그룹이라 기본 Admission Control 플러그인으로 제한이 활성화돼있다.
클러스터롤바인딩 생성
그러므로 별도의 그룹에 대한 별도의 클러스터롤바인딩을 만들어줄 것이다.
k get clusterrole admin -oyaml
그리고 cluster-admin이라는 롤을 바인딩해줘도 되지만, 이것은 단순히 *을 통해 모든 권한을 가지도록 세팅돼있으므로, 보통의 관리자 권한을 가진 정도의 admin 클러스터롤을 사용해본다.
이 클러스터롤은 aggregationRule을 통해 aggregate-to-admin
이라는 라벨이 붙은 모든 클러스터롤의 값을 획득하도록 돼있다.
반대로 말하자면 관리자에게 어떤 권한을 추가하고 싶다면 해당 라벨을 붙인 클러스터롤을 만들어주기만 하면 이 클러스터롤에 알아서 권한이 추가되므로, 관리를 유연하게 할 수 있다는 장점이 있다.
참고로 이 클러스터롤은 클러스터 범위의 리소스에 대해서는 접근 권한을 세팅하지 않는다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: test-admin-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:tester
이런 식으로 system:tester
그룹에 속하는 주체는 admin 클러스터롤을 받도록 세팅했다.
키 세팅
rsa 말고 한번 다른 방법의 키를 만들어보자.
openssl genpkey -algorithm ed25519 -out private.pem
rsa는 따로 서브 커맨드를 지원하는 방면, ecc를 활용하는 알고리즘에 대해서는 아직 따로 커맨드를 지원하지는 않으므로 그냥 범용적 커맨드를 써야 한다.
ecc를 이용하면, 키 크기가 어무막지하게 작아진다.
마찬가지로 공개키도 엄청 짧다..!
openssl req -new -key private.pem -subj "/CN=test-user/O=system:tester" -out csr.pem
간단하게 CSR 요청서를 만들었다.
openssl req -in csr.pem -text -noout
간단하게 내용물을 꺼내보면 이렇게 생겼다.
CSR 양식 파일 작성
cat csr.pem | base64 | tr -d "\n"
다음으로 해줄 작업은 CSR 오브젝트 양식 파일을 작성하는 것이다.
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: test-csr
spec:
signerName: kubernetes.io/kube-apiserver-client
request: 위 출력물
usages:
- digital signature
- key encipherment
- client auth
signerName은 기본적으로 제공되는 것이 한정돼있는데, api 서버에 접근하는 일반 클라이언트는 저렇게 서명자 이름을 지정하면 된다.
(커스텀 서명자를 지정하는 것도 가능은 하다)
여기에서 바로 subject 이름과 그룹을 명시하는 것도 가능한데, 현재는 이미 csr 파일 자체에 관련 정보가 들어가있으니 세팅하지 않는다.
인증서 받아 테스트
현재 csr 오브젝트가 만들어진 것이 확인된다.
k certificate approve test-csr
어떤 서명자에 대한 요청들은 컨트롤러 매니저에 의해 자동 승인이 되기도 하나, 이 서명자에 대해서는 해당 사항이 없으므로 직접 승인을 해줘야 한다.
참고로 승인을 하기 위해서는 승인과 관련한 적절한 권한을 가지고 있어야 한다!
승인이 완료되고, 바로 발행이 완료됐다는 말이 나온다.
그럼 실제로 인증서가 status 필드에 들어가게 된다.
마찬가지로 이값은 base64로 인코딩돼있기에, 뜯어보고 싶다면 먼저 디코딩을 해야 한다.
인증서 내용을 뜯어보면, 세팅한 값 그대로 내용이 들어가 있는 것이 확인된다!
kubectl get csr test-csr -ojsonpath={.status.certificate} | base64 -d >> cert.pem
이제 테스트 유저를 위한 모든 필요한 값들이 모였다.
k config set-credentials test-admin --client-key private.pem --client-certificate cert.pem
config 파일을 직접 수정하는 것도 가능하지만, kubectl 차원에서 세팅하는 기능도 지원해준다.
파일 경로를 사용하게 되는 경우, 위에서 말했듯이 -data
라는 접미사가 빠진다.
k config set-context test-context --cluster kubernetes --user test-admin
이제 새로운 컨텍스트를 만들고 해당 컨텍스트를 이용하면 된다.
SelfSubjectReview를 해보면 이렇게 값이 제대로 들어간 것이 확인된다.
k run test --image nicolaka/netshoot -ti -- zsh
파드를 만드는 등의 행위도 제대로 동작하는 것이 확인된다.
그러나 admin 클러스터롤은 클러스터 범위의 리소스에 대한 접근 권한이 들어가있지 않은 관계로, 관련한 동작은 수행할 수 없다.
그러나 어떤 네임스페이스에 대해 조작할 수 있는지를 지정한 건 아니라 다른 네임스페이스에 대해서도 접근할 수 있긴 하다.
k access-matrix
이건 krew를 통해 설치한 플러그인으로, 어떤 리소스에 어떤 조작을 할 수 있는지 간단하게 표시해주기에 매우 유용하다.
처음 세팅을 진행할 때 사용된 유저는 모든 리소스에 접근이 가능하다.
그러나 방금 세팅된 유저는 꽤나 사용할 수 있는 리소스가 제한적이다.
이 admin은 클러스터를 관리하기 위한 롤이 아니니까 당연하기도 하다.
현재는 클러스터롤바인딩을 만들었지만 보통 롤바인딩으로 특정 네임스페이스의 관리자로서만 권한을 주는 식으로 활용한다.
api 구조
관련 간단 실습
- 인증 하나 인가 하나 승인 하나
인증 - 키클록 oidc
Cert Manager 활용해보기
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.31/deploy/local-path-storage.yaml
키클록에 사용되는 postgre 기동을 위해, 간단하게 로컬 패스 프로비저너를 사용한다.
storageclass.kubernetes.io/is-default-class: "true"
자동으로 생긴 스토리지 클래스가 디폴트가 되도록 어노테이션을 달아준다.
auth:
adminUser: admin
adminPassword: "admin"
tls:
enabled: true
autoGenerated: true
adminRealm: "master"
production: false
httpRelativePath: "/"
service:
type: NodePort
http:
enabled: true
externalTrafficPolicy: Cluster
ingress:
enabled: false
adminIngress:
enabled: false
keycloakConfigCli:
enabled: false
postgresql:
enabled: true
auth:
postgresPassword: ""
username: bn_keycloak
password: ""
database: bitnami_keycloak
secretKeys:
userPasswordKey: password
architecture: standalone
helm install keycloak bitnami/keycloak -n keycloak --create-namespace -f keycloak-helm.yaml
해당 파일을 통해 키클록을 설치한다.
간단하게 테스트하라고 설치 후에 포트포워딩을 어떻게 하면 되는지까지 친절하게 알려준다.
이걸 이용하는 것은 좋으나 주의해야할 사항이 하나 있다.
OIDC로 ID 토큰을 받으면 이슈어가 페이로드에 담기게 되는데, 이때 해당 서버로 토큰을 달라고 요청할 때 사용된 도메인 이름이 이슈어에 들어가게 된다.
그러므로 향후 api 서버가 검증할 url에 표시될 도메인과 똑같도록 사전에 /etc/hosts
에 세팅을 해주는 게 좋다.
똑같이 세팅을 했다면, keycloak 이란 이름의 서비스가 keycloak 이란 네임스페이스에 생성이 됐을 것이고, 이 경로를 api 서버가 활용하게 될 것이므로 이것을 활용한다.
127.0.0.1 keycloak.keycloak
이런 식으로 값을 추가해주면 된다.
또한 현재 진행한 간단한 세팅에서는 인증서가 자동생성되도록 만들었기 때문에, oidc 설정 파일에도 이에 대한 세팅을 추후에 넣어줘야 한다.
k -n keycloak get secrets keycloak-crt -oyaml
이로부터 생기는 시크릿 파일의 ca 인증서를 미리 저장해두자.
기본 세팅
참고로 나는 키클록에 대해서는 OIDC 인증 제공자로서 활용할 수 있을 정도로만 간략하게 공부했다.
세팅 역시 다른 분의 글을 상당히 많이 참고했다.[1]
내 realm을 먼저 만든다.
한 realm 안에는 여러 클라이언트를 둘 수 있는데, 이 클라이언트가 각각 인증을 수행하는 큰 단위가 된다.
키클록을 통해 여러 서비스의 인증을 수행하고 싶다면 여러 클라이언트를 만드는 식으로 세팅하면 되는 것이다.
그래서 이 클라이언트를 이제 만들어준다.
기본으로 존재하는 클라도 상당히 많은 것이 보인다.
OIDC로 사용하기 위해서는 Client Authentication을 활성화해줘야 한다.
여기에서 direct access grants가 있으면 비밀번호로 토큰을 받을 수 있다.
만들어지면 바로 credential에서 client secret 값을 메모해둔다.
이 값은 나중에 이 클라이언트를 이용해 토큰을 발급받을 때 사용하게 될 것이다.
그 다음에는 바로 옆 탭 roles를 들어가 롤을 하나 만든다.
유저 세팅
다음은 유저를 하나 만든다.
여기에서 username만 필수라고 해서 다른 부분을 세팅하지 않으면 유저가 완전히 세팅되지 않았다는 에러를 보게 되므로, 미리 세팅을 해두자.
비번도 만들어준다.
이후에 위에서 만든 롤이 처음 만든 scope 속에 들어있는 것을 확인할 수 있다.
이걸 바로 붙여준다.
ID 토큰 확인
curl -k -X POST https://keycloak.keycloak/realms/test-realm/protocol/openid-connect/token \
-d grant_type=password -d client_id=kubernetes-auth -d username=zerotay -d password="1234" -d \
scope=openid -d client_secret=f4n2pYSsjOr3Epfh04ASwHPCU9tIrXXD | jq -r '.id_token'
이렇게 해서 나와준다면 성공이다!
값을 jwt.io에 넣어보면, 내 유저의 값이 정상적으로 나오는 것을 확인할 수 있다.
api 서버 OIDC 세팅
api 서버에 인자를 넣어서 설정하는 방법도 좋지만, 세밀하게 설정을 제어하기 위해서는 설정 파일을 사용하는 것이 가장 좋으므로, 해당 방법을 사용한다.
파일의 경로를 이런 식으로 추가해준다.
실습을 간단히 하기 위해 이미 마운팅된 호스트 노드 경로에 파일을 위치시켰다.
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
jwt:
- issuer:
url: https://keycloak.keycloak/realms/test-realm
audiences:
- kubernetes-auth
audienceMatchPolicy: MatchAny
certificateAuthority: |-
-----BEGIN CERTIFICATE-----
키클록 설치할 때 받은 ca.crt를 base64로 디코딩해서 넣자
-----END CERTIFICATE-----
claimValidationRules:
- expression: 'claims.iss.contains("keycloak")'
message: Issuer expected Keycloak!!!
claimMappings:
username:
claim: "preferred_username"
prefix: ""
userValidationRules:
- expression: "!user.username.startsWith('system:')"
message: 'username cannot used reserved system: prefix'
간단하게 이런 식으로 작성을 해주었다.
검증 규칙은 CEL 방식인데, 이건 기회가 되면 다루겠다.
필수적으로 세팅이 돼야 하는 것은 이슈어 부분에 url, audiences.
대충 설명하자면 이슈어 문자열에 키클록이 들어가지 않으면 토큰 자체가 검증에 실패하고, 인증될 때 사용될 유저 이름은 preferred_username 클레임을 사용한다.
유저 이름이 system으로 시작하면 유저 검증 실패를 하도록 만들었다.
실제로 이렇게 세팅된 값은 api 서버의 로그로 남게 된다.
(audit에 남는 것이 아님을 유의하자)
세팅 시 유의할 점 중 하나는 클레임 검증 규칙에서 표현식의 대상이 되는 값과 클레임 매핑시 사용되는 값, 유저 검증 규칙에 사용되는 값이 전부 다르다는 것이다.
클레임 검증 시에는 claims를 앞단에 쓸 수 있지만 클레임 매핑부터는 한 깊이를 들어가서 값에 접근한다.
유저 검증 규칙에서는 매핑된 값을 토대로 user 객체가 생성되어 이것을 활용해야 한다.
말로 하면 복잡해보이지만, 막상 해보면 정말 복잡하다 ㅅㅂ
디버깅을 그래도 빠르게 할 수 있긴 하지만, 최소한 출력이라도 할 수 있게 창구라도 주면 좋을 텐데..
추가적인 세팅
여기에서 몇 가지 꼼수가 필요하다..
이전에 세팅한 것들을 살짝 날려먹어서 어쩔 수 없이 돌려막기식 세팅을 한 것들이 있는데 조금 더 잘 정제된 세팅 환경에서는 이렇게까지 불편한 세팅을 할 필요는 없을 것이다.
- https 통신
- 기본적으로 통신은 반드시 https 통신이어야 하고, 443 포트 사용이 강제된다.
- 하지만 위에서는 노드 포트를 사용했기 때문에 이것을 443포트로 굳이 굳이 연결시켜주는 과정이 필요하다..
- 이를 위해 마스터 노드에서 kubectl 포트포워딩을 활용한다.
kubectl -n keycloak port-forward svc/keycloak 443:443 --address 192.168.80.11
- api 서버는 컨테이너이고 localhost 인터페이스는 호스트와 공유하지 않기 때문에, 호스트의 다른 ip 주소를 바인딩해야 한다.
- 도메인 세팅
- 요청을 날리는 호스트는 내 그냥 로컬이고, api 서버는 vm에 띄워져있기에 단순하게 ip를 사용하는 것이 불편하다.
- 물론 못할 건 없고, https라고 해도 ip 기반으로도 연결은 가능하긴 하다.
- 그래서 노트북 쪽에서
/etc/hosts
파일을 수정해준다.- 이렇게 하면 vm에서도 도메인을 못 찾아 로컬을 타고 나가려할 때 이 파일의 영향을 받는다.
- 위의 세팅대로라면
192.168.80.11 keycloak.keycloak
으로 해주면 된다.
- 요청을 날리는 호스트는 내 그냥 로컬이고, api 서버는 vm에 띄워져있기에 단순하게 ip를 사용하는 것이 불편하다.
이미 로드밸런서 컨트롤러를 구축해둔 환경에서는 이렇게까지 귀찮게 세팅을 할 필요가 없을 것이다.
또 클러스터 도메인을 노드에서도 활용할 수 있게 도와주는 툴을 활용하고 있다면 이렇게 도메인 세팅을 귀찮게 할 필요도 없어진다.
최종 테스트
kube_url = "https://192.168.80.11:6443/api/v1/namespaces/default/pods/debug?limit=500"
def request_in_oidc():
url = 'https://keycloak.keycloak/realms/test-realm/protocol/openid-connect/token'
body = {
'grant_type': 'password',
'client_id': 'kubernetes-auth',
'client_secret': 'f4n2pYSsjOr3Epfh04ASwHPCU9tIrXXD',
'username': 'zerotay',
'password': '1234',
'scope': 'openid'
}
response = requests.post(url=url,data=body, verify="")
id_token:str = response.json()["id_token"]
print("\nThis is IdToken", id_token[:20],"...")
headers = {
"Content-Type": "application/json",
"Authorization" : "Bearer " + id_token.strip()
}
response = requests.get( url= kube_url, headers=headers, verify="",)
return response
def main(args: argparse.Namespace):
type: AuthType = args.type
print("Test for ", type)
response = requests.models.Response()
match type:
case AuthType.oidc:
response = request_in_oidc()
print(response.status_code)
print('Response Header :::::')
print(json.dumps(dict(response.headers), indent=2))
print('Response Body :::::')
print(json.dumps(response.json(), indent=2))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="put subcommand")
parser.add_argument("--type", type=AuthType, required=True, help="x509, static, bootstrap, webhook, oidc, satoken")
args = parser.parse_args()
main(args)
이건 내가 흔히 테스트를 할 때 사용하려고 만든 코드의 일부이다.
위의 세팅을 기반으로 이 파일을 실행하면..
이런 식으로 403 에러와 함께 실패하면 성공이다!
인증이 되지 않았다면 401에러가 났을 것이고, 현재 에러는 인가되지 않음을 뜻하는 403 UnAuthorized이다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: test-admin-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: zerotay
기왕이면 제대로 결과를 보고 싶으니, 간단하게 이렇게 인가 설정을 해준다.
이제는 결과가 제대로 나오는 것이 확인된다.
인가
파이로 관리
권한 상승 시키기
워크로드로부터 다른 권한 획득하기
승인제어
네임스페이스마다 기본 sa 만들고, 파드조회 권한 부여
hostpath는 거부시키기
keyverno
어떤 유저가 escalate 동사가 있어도, 아무리 그래도 secret list는 못하게 role만들기 막기
eks 인증
eks 인증
기본 확인
콘솔에 eks쪽 access 탭에 들어가면 관련한 정보가 확인된다.
현재 액세스 방식은 두 가지가 혼용되고 있다.
우선되는 것은 eks api로, 여기에서 인증을 수행할 수 없을 때 configmap을 참조하게 된다.
액세스 엔트리에는 다양한 정보가 들어가 있는데, 노드와 이 클러스터를 만든 사용자에 대한 인증을 위한 설정이 돼있다.
첫번째 엔트리는 콘솔을 통해 클러스터를 확인할 수 있도록 하는 엔트리이다.
그리고 가장 마지막 엔트리가 현재 사용자인 나를 위해 설정된 엔트리로, 관리자 정책이 부여되는 것을 확인할 수 있다.
aws eks list-access-policies
aws eks list-access-entries --cluster-name $CLUSTER_NAME
eks 차원에서 제공하는 관리형 접근 정책에는 여러 종류가 있어서 이것을 활용해도 된다.
현재 만들어진 엔트리를 cli로도 확인할 수 있다.
aws eks list-associated-access-policies --cluster-name --principal-arn
해당 엔트리에 어떤 정책들이 엮였는지 확인하고 싶다면 이렇게 해주면 된다.
추가적으로 아직 남은 configmap도 살짝 보자면..
k -n kube-system get cm aws-auth -oyaml
configmap 파일 내용물을 보면 노드들의 arn이 클러스터에서 node로 매핑되도록 된 것을 확인할 수 있다.
액세스 엔트리 실습
configmap 방식은 곧 deprecated될 거라 굳이 실습하지 않고, 액세스 엔트리를 사용하는 방식을 사용해본다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-viewer-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-viewer-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pod-viewer-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: pod-viewer
먼저 클러스터롤과 바인딩을 만들어준다.
aws iam create-user --user-name testuser
aws iam create-access-key --user-name testuser
aws configure --profile test
다음 유저를 만들어준다.
프로필을 따로 설정하여 여러 유저로 다룰 수 있게 프로필 세팅을 진행했다.
이 유저는 IAM에 대한 어떠한 정책도 가지고 있을 필요가 없다.
eks get token은 자격증명을 가지고만 있다면 실행이 가능하기 때문이다.
...
- context:
cluster: arn:aws:eks:ap-northeast-2:134555352826:cluster/terraform-eks
user: eks-test
name: eks-test
...
- name: eks-test
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-2
- eks
- get-token
- --cluster-name
- terraform-eks
- --profile
- test
- --output
- json
command: aws
env: null
interactiveMode: IfAvailable
provideClusterInfo: false
그 다음 kubeconfig에서 유저와 컨텍스트를 하나 추가한다.
유저의 부분에서 프로필을 사용하는 식으로 지정한 것을 확인할 수 있다.
현재 컨텍스트만 바꿔주면, 이제 내가 하는 테스트 유저로서 동작을 할 수 있게 된다!
CLUSTER_NAME=terraform-eks
ACCOUNT_ID=
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser
aws eks create-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::${ACCOUNT_ID}:user/testuser --kubernetes-groups pod-viewer
마지막으로 액세스 엔트리를 만들어 매핑을 진행한다.
보다시피 이 유저는 정직하게 default 네임스페이스의 pod를 보는 권한만 부여받았기에, 관련한 동작만 수행할 수 있다.
콘솔에서도 해당 유저는 그룹만 지정받는 것을 확인할 수 있다.
워크로드의 aws 리소스 접근
apiVersion: v1
kind: Pod
metadata:
name: eks-iam-test1
spec:
containers:
- name: my-aws-cli
image: amazon/aws-cli:latest
command: ['tail', '-f', '/dev/null']
# automountServiceAccountToken: false
terminationGracePeriodSeconds: 0
간단하게 테스트를 진행할 수 있는 파드를 만들었다.
기본적으로 파드에 아무런 설정을 하지 않아 default 서비스 어카운트가 사용됐다.
아무런 설정을 하지 않으면 보다시피 aws cli를 사용할 수 없다.
물론 이건 credential 파일이 없어서 일어나는 이슈이긴 하다.
IRSA 설정
eksctl create iamserviceaccount \
--name my-sa \
--namespace default \
--cluster $CLUSTER_NAME \
--approve \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn' --output text)
직접 iam 롤도 만들고, 서비스 어카운트에 어노테이션도 붙이고 하는 작업을 전부 수작업으로 진행할 수도 있지만, 간단한 명령어를 이용하는 것도 가능하다.
그럼 이렇게 알아서 서비스 어카운트가 만들어지고, 관련 어노테이션이 들어간다.
또한 iam에도 적절한 롤이 생성된 것을 확인할 수 있다.
k get mutatingwebhookconfigurations.admissionregistration.k8s.io pod-identity-webhook -oyaml
뮤테이팅 어드미션 웹훅 설정에는 스킵을 하도록 설정된 파드 이외에 모든 파드의 생성 요청에 대해 적용되는 규칙이 설정돼있다.
url을 보았을 때, api 서버와 같은 인스턴스에 23443 포트에서 동작을 수행하는 것으로 보인다.
아무래도 클러스터 리소스로 만들었을 때는 AWS 사용자도 해당 컨테이너 정보를 확인할 수 있게 되기 때문에 따로 구동한 듯하다.
확인 및 테스트
다시 만든 파드에는 새로운 볼륨이 추가된 것을 확인할 수 있다.
또한 환경변수로 몇 가지 값이 들어가는데, 가장 아래 환경변수가 있을 때 aws sdk와 cli는 알아서 웹 기반 자격 증명을 시도하게 된다.
이제는 명령이 정상적으로 수행된다는 것을 확인할 수 있다.
기다리다보면 해당 명령이 수행된 것이 클라우드트레일에도 남는다.
파드 아이덴티티
워커 노드에 적용되는 기본 정책에는 파드 아이덴티티 기능을 활용하기 위한 기본 권한이 있다.
확인
k -n kube-system get daemonsets.apps eks-pod-identity-agent -oyaml
2703, 80 포트를 사용하는 데몬셋이 확인된다.
ss -ntlp
호스트 네트워크를 사용하고 있기 때문에, 직접 인스턴스에 접속해서 포트 상태를 보더라도 관련한 정보가 보인다.
특정 ip를 사용하도록 아예 세팅이 돼있는데, 가상 인터페이스를 통해 아예 고정돼있는 것도 확인할 수 있다.
사용 테스트
참고로 테라폼에서는 아래와 같이 모듈을 사용할 수 있다.[2]
module "aws_vpc_cni_ipv4_pod_identity" {
source = "../../"
name = "aws-vpc-cni-ipv4"
attach_aws_vpc_cni_policy = true
aws_vpc_cni_enable_ipv4 = true
# Pod Identity Associations
association_defaults = {
namespace = "kube-system"
service_account = "aws-node"
}
associations = {
ex-one = {
cluster_name = module.eks_one.cluster_name
}
ex-two = {
cluster_name = module.eks_two.cluster_name
}
}
tags = local.tags
}
굳이 이걸 사용해야만 하는 건 아니다.
다만 각종 애드온에 대해서 aws에서 미리 관리형으로 정책을 제공해주지 않다보니.. 귀찮은 정책을 일일히 작성하거나 받아오는 것보다는, 아무래도 간단하게 모듈로 관리하는 게 편하다는 정도의 생각이다.
(애초에 정책이랑 롤 미리 만들어주기만 했으면 굳이 모듈로 안 썼다.)
하지만 이에 앞서서 직접 만들어 세팅하는 것을 먼저 해보자.
eksctl create podidentityassociation \
--cluster $CLUSTER_NAME \
--namespace default \
--service-account-name my-sa \
--role-name s3-eks-pod-identity-role \
--permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--region ap-northeast-2
eksctl을 사용할 경우 매우 간단하게 어소시에이션을 만들 수 있다.
콘솔을 쓰면 원하는 네임스페이스와 서비스 어카운트를 지정하는 것이 훨씬 간단하다.
만들고 나면 콘솔에서 쉽게 해당 정보를 확인할 수 있다.
k create sa my-sa
다만 파드 아이덴티티 설정은 알아서 서비스어카운트를 만들어주지는 않으므로 직접 만들어준다.
이제 다시 파드를 생성해보면..
일단 환경변수로 또 비슷하게 값이 주입된 것을 확인할 수 있다.
크게 상관은 없겠다만 왜 굳이 저 ip를 항상 고정하도록 한 것인지 조금 궁금하다.
아울러 이번에도 서비스 어카운트 토큰이 추가적으로 들어가게 된다.
다시금 aws에 요청을 날려보면 이번에도 성공적으로 리소스 접근이 성공한 것을 확인할 수 있다!
클라우드트레일에는 podidentity 롤 설정 이벤트가 남는데, 노드의 인스턴스 프로필이 사용돼 유저 이름에 인스턴스 이름이 남는 것을 확인할 수 있다.
keti eks-iam-test1 -- cat /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
마지막으로 실제 토큰이 어떻게 생겼는지는 jwt.io에 토큰을 넣어서 확인해볼 수 있다.
이건 기본으로 주입되는 서비스어카운트 토큰인데, 사실 페이로드에서 다른 값은 청중을 뜻하는 aud값 뿐이다.
keti eks-iam-test1 -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | openssl x509 -noout -text -pubkey
기본 서비스어카운트 토큰에 대해서는 인증서 파일이 같이 딸려오므로 공개키를 꺼낼 수 있는데, 파드 아이덴티티의 경우 마운팅이 되지 않기 때문에 비교를 할 수 없었다.
다만 어차피 api 서버에서 모든 작업이 이뤄지고, 내가 알고 있는 한 개별 서비스 어카운트에 대해 개별 인증서를 적용하는 설정이 없기 때문에 아마 같은 키로 서명되지 않았을까 생각해본다.
스터디
이번엔 바스티온 2개를 둘 것이다.
호스트 엔드포인트 100,200.
데봅 2명이라 가정하고, 한명에게 권한을 주는 시뮬레이션
kindk8s를 쓸거라, 하나는 xlarge로 만든다.
오늘 주요 키워드는 암호.
정보 보안 3요소 - 기밀성, 무결성, 가용성
이중 기밀성이 흔히 말하는 보안이긴 하다.
AAA 액세스 제어.
인증인가 감사.
그럼 어드미션 컨트롤은?
암호 관련 정리를 참고하기 좋겠다.
암호화는 알고리즘과 키로 이뤄진다.
어떤 식으로 암호화한다는 게 알고리즘.
이때 쓰는 것이 키.
안전하게 키를 전달하고, 안전한 알고리즘을 쓰는 게 중요하다.
https://emn178.github.io/online-tools/rsa/sign/
암호화 실습
대칭키부터.
단순 대칭키는 중간자 공격에 취약해서, 신원 확인이 필요하다
해시. 단방향.
해시 적용 결과값을 다이제스트라 부른다.
해시는 일방향성.
그리고 충돌내성
대표적인건 md5, 달리 말해 sha
레인보우 테이블을 이용하면 역산이 되긴 한다.
이걸 막고자 솔트를 쓴다.
핵심은 공개키 알고리즘.
대표적인 알고리즘은 RSA.
예제 사이트에서 암호화는 클라 입장이라 공개키를 넣는다.
이건 개인키를 가진 서버만 해독 가능
(그럼 ecdsa는 뭐지? 얘는 암호화복호화가 없다.)
공개키 자체는 연산이 필요해서, 공개키로 대칭키를 서로 공유하는 방식을 쓴다.
대칭키를 노나는 방법은 4개.
직접, 디피헬만, rsa, 키분배센터
프리마스터시크릿라는 난수.
단순 rsa는 중가자공격이 가능하다.
그래서 쓰는 게 논세
클라쪽에서 자신의 난수 n1을 같이 암호화해 보낸다.
서버는 응답할 때 n1과 자신의 난수 n2을 같이 또 보낸다.
이러면 서버가 개인키로 까서 n1을 확인했다는 것이 증명된다.
그럼 클라는 이제 n2랑 대칭키를 같이 보낸다.
그럼 서버는 n2를 보고 또 클라를 확인할 수 있다.
그럼 이제 이 대칭키로 통신하면된다!
이 방식이 모든 클라랑 이뤄지면, 키가 엄청 많아진다.
그래서 kdc,가 생기는 거다.
키분배센터
이게 발전해서 pki가 된다.
메시지 인증 방법은?
해시.
공개된 해시함수로 자신의 평문을 해시돌려서 같이 보냄
그러면 받은 측이 평문을 또 해싱해보고 확인한다.
더 발전되면 mac
대칭키를 솔트삼아 해싱
더더발전하면 hashed mac
이게 ssh등에 쓰인다.
이걸 잘 알아ㅑ 할 듯
근데 또 무결성만 증명되는 건 문제가 있어서 나오는 게 디지털 서명
부인봉쇄가 필요하다!
메시지의 변조는 확인할 수 있는데 결국 상대 신원을 또 모르니까..
클라 입장에선 서버를 믿을 수 없고, 맘대로 생떼쓸수 잇음
반대로 서버 입장에서도 클라가 받은 데이터가 위조된 것인지 명확히 할 수 가 없다.
즉, 부인을 할 수있는 방법이 없다는 것.
그래서 부인봉쇄가 필요하다.
이게 가능한 게 rsa 디지털 서명.
평문이랑 개인키로 암호화한 평문을 같이 보낸다.
공개키가 있는 자는 복호화해보고, 평문이랑 비교해서 신원을 확인할 수 있다.
이 평문이야 해커가 알수는 있지만, 개인키는 없으니 똑같이 암호화가 안 된다.
부인 방지를 할 수 ㅣㅇㅆ게된다는 것.
메시지 내용도 변경할 수 없으니 무결성까지 확보.
위에서 hashed mac을 말했는데, 해시된 부분을 이렇게 암호화해서 쓴다.
그리고 보내는 방식은 hashed mac과 일치한다.
그럼 이렇게 암호화한 부분을 디지털 서명이라 부른다.
이 해시 방법에는 md5, sha-1이 있다.
(해시는 왜 하지? 평문)
이런 방식을 쓰는게 바로 x509.
여기에는 유휴기간과 공인 인증기관 개념까지 들어간다.
공개키를 신뢰성있게 배포하기 위해 존재한다.
ca는 루트 인증서, 자체 서명.
서버는 공캐키를 등록요청한다.
그럼 ca는 본인 개인키로 암호화해서 인증서 발급.
서버는 이제 이걸 인증서로 쓴다.
클라는 서버의 답을 받았을 때, 인증서를 확인한다.
ca로 확인해서, 비로소 서버의 찐 공개키를 얻을 수 잇다
x509가 인증서 표준 형식이고, pki가 이런 시스템을 총칭한다.
이제 인증서는 또 계층 구조를 가진다.
하나의 ca만 일하면 힘드니까, 하위하위를 두는 방식이다.
https에서는 pms를 공유하는 과정인 것.
그다음엔
ㅐ
certmanager쓰면 csr 승인 자동화할 수 있다.
이거 무조건 해봐야할 듯.
고통 그만 받고..
이다음부터는 실습 따라감
irsa는 뮤테이팅으로 env값을 넣어주더라.
projected는 동적 업데이트되는가?
파드아이덴티티도 뮤테이팅으로 데몬셋으로 요청이 전달되게
근데 잘보니까 볼륨도 넣어주는 것 같다.
아마 이건 projected일까?
모르겠지만, 최소한 동적 업데이트가 될것이다.
이걸 내 워크로드가 활용하려면, 해당 경로를 활용해야 한다.ㅣ
액세스 엔트리는 관리자 입장에서 eks api를 이용해 설정하는 수밖에 없는 것으로 보였는데요, 클러스터 차원에서 설정하는 방법은 없을까요?? crd로 설정한다던가..
irsa가 불편하니 파드아이덴티티가 생긴 것처럼.
MIIDKTCCAhGgAwIBAgIINWk6zPahxQIwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UEAxMKa3ViZXJuZXRlczAeFw0yNTAzMDkxMjA4MDlaFw0yNjAzMDkxMjEzMDlaMDwxHzAdBgNVBAoTFmt1YmVhZG06Y2x1c3Rlci1hZG1pbnMxGTAXBgNVBAMTEGt1YmVybmV0ZXMtYWRtaW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkEXpgfQyYqcVFN8BL3L44DP8UZCKrihNWroWgXnPLJ28KMsUmX8aSg3fI11fJ4oG6PZnONAbEj7VrC7434Z+MT4aZFS2Pi0x4CJzEICZk4d5zyXomFmwkQLS47fuJACXDqd77w2URT1juBRooYQnp5AI7r2AjORzZqkQDfGm8eXAl/buIk71byjyTmPdOftjEKOngyzmoh5+lKD+9fR7HxVvqWdAIGCQhUmYAGcuQhWctJRHabuJMGuFWkjRfeAXyV26x+iVEw/0mKJYVpN2kYRTzuDBuqCwmwDR5qShoUErIMujjCfbxa8d0/LR8Zpq0TijeA5rSRSuhvCQiqSgBAgMBAAGjVjBUMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFKBeBqj/MbrSSQDPgWKVNc/enrHUMA0GCSqGSIb3DQEBCwUAA4IBAQAYzqNb8GIXtGEcnMt7mU7375aATXpNGlXwjYXYE6bHE+9646nuioDQ4dhb0dOlTy3S2yU+dMK4r4ZDAaNmBmGVJz9wUQrot3b/gRc7bGum2ygHRtNB59EbeqkPqGheqUkBfjwPf2bmcsGD46dNPibNKrQa3XnqN76Ja98i1X6aPlwNyWrr+S74FUx0dmCqNIASA+wqRwmAnfEj6ngnVfTvQgj/B0ddz/kX/PTW3MqDfY43EeyjRaXaZqBOPqtjBzl+lZPIrw6MxgU38LZz5UWEmWaWiWhBtJoTX9hFDmYhDh9lu+d01XxvDca9nXb6ZX/tN2F7n+YDfUStTd/jaEO0
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIeNrBKPKNzuG3Fc
1kV4hXuToXSVRWu6zpDZ/O3r4c4Lyv4lrb/3I0a4pPfPN0MiASbBXx/s8hUcMqWg
DS3ZwH8QsljiawNEF4OYIGnTgJmBuWdR5e+twmw6IAoNAY4kyU1x6BxJF3uDFm9K
a1Rc+Guk489lDfXtFBaoCHowJP4NAgMBAAECgYADyHNl7TLhv49qgYHFXJC1GzCl
VUkjsYn0RvElHrElk/StVRXdRqNoZNzNwa20JO3NTBZAiNdUuX28W5QilHInzy5+
2ub/a+S//pX96qtK1nz4i9N4v/h+KH5ijUuSso/JyGGlQyVCUl12g5eOBqIuauGI
qn8QLLpz6AlX4Ek/4QJBANPskiqSSIUPijbcaYbVXa4+rnPeuYCwXtx42m3YlQyQ
nIFqFOwfRus/FuPTIrFBu3JirsjcqGkGWIInzL8YDfkCQQCjvumnfbSaVfOYInqJ
ziePwxSTANUd64sO6l0n4/hgPI/iJ9vc05vNMuaZiG5j4aaruQmmumBwylRnfMDP
F0W1AkEAiPl0M+3ez5oOtIzb7BlGdpPu/9dqQMI+XfQDAlKla7ygW4ksQr2oge6C
JfjWiIk61aDw5cSxWUiPtnhw/uZWSQJAZbmS2pTDgCXpgRfaXIYQGcWtoG2h+EZ+
SzPZz5BWmyLEmFD+y79CSUZX8AXL3o0ux/vaPRQIGcn4iZn9BiqFCQJBALRTBX+Q
FbqWODH/64Yg1SGUKekm64I96MkBUB+WOszS8W+mGvlBAMBIL35QsyN5BbcF/oD2
h02BYGnTaRjVbrk=
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHjawSjyjc7htxXNZFeIV7k6F0
lUVrus6Q2fzt6+HOC8r+Ja2/9yNGuKT3zzdDIgEmwV8f7PIVHDKloA0t2cB/ELJY
4msDRBeDmCBp04CZgblnUeXvrcJsOiAKDQGOJMlNcegcSRd7gxZvSmtUXPhrpOPP
ZQ317RQWqAh6MCT+DQIDAQAB
-----END PUBLIC KEY-----
kubectl krew install access-matrix rbac-tool rbac-view rolesum whoami
ㅐ
관련 문서
이름 | noteType | created |
---|